iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
1

寫在前面

先給你們看一張圖

https://ithelp.ithome.com.tw/upload/images/20200915/20127836Th49vOeszs.png

不只是過去,現在php仍舊統治著網路世界

當你還在其他語言研究框架跟如何劃分MVC架構程式碼的時候,php已經打造好網站上線了
當時祖克伯寫facebook時為了快速上線就是選用php,而同時php也能讓你快速的完成某些功能
這也是為什麼其他社群網站有的東西很快就可以在FB上面看到(直播,限時動態等等)
下面是各個大型網路服務商選用的後端語言

https://ithelp.ithome.com.tw/upload/images/20200915/20127836yLJFrvA9mO.png

七大龍頭(其實是六大,google跟youtue算一個)有四家選了php作為後端

不過php的缺點就是維護極其的困難
前端與後端的構造混亂(你們馬上就會體會到了)
每個接手的工程師都要從寫的架構著手才能理解整個網站在做什麼
還有由於php是直譯語言,因此當網站網站的流量一大時就很容易卡頓
動態語言的特性也讓php難以成長為大型項目(因此facebook才會做出hack)

不過當今天你只是想做一個簡單網站,並且希望快速部屬,首選還是會是php
(不是指網頁喔,單純靜態網頁使用html,css,js就夠了,這裡指的網站是你需要儲存使用者輸入的數值,帳號密碼一類的)

當然,php也可以做一般的終端機程式或是GUI程式,不過我們今天就來學習php最常做的project--網站吧

nginx

首先,我們需要架設伺服器,這裡我們採用nginx來架設

等等,今天不用等我們先去安裝php嗎?

我們昨天已經學會docker了,可以直接使用docker,不需要再去搞那些煩人的安裝步驟
如果你不想使用docker或是你的環境沒辦法使用docker的話還是要去安裝

先去dockerhub找nginx吧
接著輸入以下指令

docker container run -p 8080:80 nginx:stable-alpine

記得nginx後面的tag要改成自己下載的tag版本

接著在你的瀏覽器上面打上localhost:8080
就可以看到

https://ithelp.ithome.com.tw/upload/images/20200915/20127836aK1B4NSfZb.png

說明一下-p這個指令代表的意思
大家應該都知道電腦在網路上有自己的門牌號碼,也就是所謂的ip
而如果你不想從外部連自己的電腦(畢竟要跑到公共網路再跑回來,會比較慢而且多此一舉)
就可以使用localhost或是127.0.0.1(你可以把剛剛的網址改成127.0.0.1:8080,應該也會通)

這時候雖然你的瀏覽器知道了你的服務的地址,但是還不知道你家的大門位置,
這個所謂的大門位置就是port號, : 後面的數字,這個數字總共有65535個,
其中給http協定,也就是網址列http://localhost的服務預設會是80,
但是通常port號1~1024是給系統管理員使用的,因此在我們本機要使用的話就會需要較高的權限
為了節省麻煩,我們會改用8080port

這也是為什麼-p的左側我們使用8080,因為左側代表我們本機的port號
而右側的80port則是nginx伺服器預設監聽的port號
因此-p 8080:80的意思就是把在docker內部的80 port給導出到我們本機的8080 port

這裡我們的伺服器就建立好了
現在使用ctrl+C關掉他,我們要把現在看到的頁面給換成我們的php程式

這時雖然container已經被關閉了,但是還沒有被刪除
使用

docker container ls -a

可以看到container的服務其實並沒有死,只是沒在運作
這時你可以使用

docker rm containerID

把container刪除
(containerID可以不用打全部,前面幾個數字就可以了,docker會自動去判斷)

接下來,我們來製作替換掉原本頁面的php頁面吧

php

在你的工作目錄開一個資料夾,並且在裡面建立一個附檔名是.php的檔案

<!DOCTYPE HTML> 
<html>
<head>
    <title>Converter</title>
</head>
<body> 

<?php
$input = $type = "";

if ($_SERVER["REQUEST_METHOD"] == "POST")
{
    if(isset($_POST["input"])) {
        $input = $_POST["input"];
    }
    if(isset($_POST["type"])) {
        $type = $_POST["type"];
    }
    switch ($type) {
        case 't2h':
            $output=t2h($input);
            break;
        case 'h2t':
            $output=h2t($input);
            break;
    }
}

function t2h($input) {
    for ($i=0; $i<16; $i++) {
        for ($j=0; $j<16; $j++){
            for ($k=0; $k<16; $k++){
                if (pow(16,2)*$i+pow(16,1)*$j+pow(16,0)*$k==$input) {
                    return returnAE($i).returnAE($j).returnAE($k);
                }
            }
        }
    } 
}

function returnAE($input){
    switch ($input) {
        case 10:
            return 'A';
        case 11:
            return 'B';
        case 12:
            return 'C';
        case 13:
            return 'D';
        case 14:
            return 'E';
        case 15:
            return 'F';
        default:
            return (string)$input ; //也可以直接回傳$input
    }
}

function h2t($input) {
    $i = 0;
    $output = 0;
    while ($i < strlen($input)) {
        $output += AEreturn($input[$i])*pow(16,strlen($input)-$i-1);
        $i++;
    }
    return $output;
}

function AEreturn($input){
    switch ($input) {
        case 'A':
            return 10;
        case 'B':
            return 11;
        case 'C':
            return 12;
        case 'D':
            return 13;
        case 'E':
            return 14;
        case 'F':
            return 15;
        default:
            return $input;
    }
}
?>

<h1>進制轉換</h1>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
    <select name="type">
        <option value="h2t">十六進制轉十進制</option>
        <option value="t2h">十進制轉十六進制</option>
    </select>
    <input type="text" name="input" />
    <input type="submit" value="Submit">
    <h3 id="output"></h3>
    <p>Click the "Submit" button and the convert input</p>
</form>

<?php
echo "<h2>轉換結果</h2>";
echo "<h3>input:".$input."</h3>";
echo "<h3>output:".$output."</h3>";
echo "<br>";
?>

</body>
</html>

結構看起來跟以前在js寫過的html好像

沒錯,因為我們今天要寫的是內嵌在html的php(當然也有不是內嵌的)
內嵌的語法我們會這樣包著

<?php
//something here
?>

先往下看到html的結構(就像看房子要先看房型對吧)

<h1>進制轉換</h1>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
    <select name="type">
        <option value="h2t">十六進制轉十進制</option>
        <option value="t2h">十進制轉十六進制</option>
    </select>
    <input type="text" name="input" />
    <input type="submit" value="Submit">
    <h3 id="output"></h3>
    <p>Click the "Submit" button and the convert input</p>
</form>

你可以把他複製貼上到另外一個.html的文件,看看他的結構

看到這段

<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">

method="post" 表示我們要使用post的方式送出我們的表單

post?

這是一種http協定下request的方式
詳細如果想知道http的request有幾種的話可以看這裡

後面的action=~~~($_SERVER["PHP_SELF"])則表示我們將使用內嵌的php語法,請html不要將我們導向別的頁面

表單?

有注意到

<form>

</form>

這個結構嗎?
裡面就是我們的表單,我們會將他當作request的內容物送出去

表單的結構之前在js的章節有談過了,這裡就不再提

往上看到php的程式碼吧

$input = $type = "";

一開始先做變數初始化
這個是php比較特殊的語法,某些語言這樣寫會報錯

$則是php宣告變數的前綴

if ($_SERVER["REQUEST_METHOD"] == "POST")

這行在判斷request送出的請求方式,也就是剛剛我們在html設置送出request的方式,
這裡如果method是post就由這裡接手

{
    if(isset($_POST["input"])) {
        $input = $_POST["input"];
    }
    if(isset($_POST["type"])) {
        $type = $_POST["type"];
    }
    switch ($type) {
        case 't2h':
            $output=t2h($input);
            break;
        case 'h2t':
            $output=h2t($input);
            break;
    }
}

注意到這個關鍵字

isset

這裡表示會去確認後面的值是否有被設置

後面的值為

$_POST["input"]

這裡會取出剛剛表單內名稱為input的值,也就是這個

<input type="text" name="input" />

如果值有被設置的話就會把他放到變數裡面

$input = $_POST["input"];

switch

switch ($type) {
    case 't2h':
        $output=t2h($input);
        break;
    case 'h2t':
        $output=h2t($input);
        break;
}

這裡的寫法跟其他語言一樣,就不多提

方法宣告

function t2h($input) {
    for ($i=0; $i<16; $i++) {
        for ($j=0; $j<16; $j++){
            for ($k=0; $k<16; $k++){
                if (pow(16,2)*$i+pow(16,1)*$j+pow(16,0)*$k==$input) {
                    return returnAE($i).returnAE($j).returnAE($k);
                }
            }
        }
    } 
}

因為php跟js一樣屬於動態型別,因此是不需要宣告回傳值與輸入值的型別的喔

另外由於輸入值我們要給他命名,因此使用$input作為變數名稱

型別

相較於js容易在字串與數字上出錯,php對於字串有別的作法
看到這段

return returnAE($i).returnAE($j).returnAE($k);

還記得returnAE是回傳十進位轉成十六進位並且回傳字串吧,
php裡面要把字串相加 (接在一起) 是使用 .
比起js數字跟字串都使用+來說更容易讓我們分辨這行在做什麼

也因為是弱型別的關係

default:
    return $input ; // return (string)$input; //可以不轉型

returnAE輸入是整數,卻可以把他當作字串回傳

AEreturn的這段也是同樣道理

default:
    return $input; //將字串當作整數回傳

以及這裡

pow(16,2)*$i+pow(16,1)*$j+pow(16,0)*$k==$input

將數字與字串比對

都是弱型別常見的作法

邏輯及迴圈

基本上switch if這種常見的邏輯大家都差不多
沒什麼特別的

for跟while也是

輸出

php的輸出就有意思了
echo實際上並不是一個方法而是一個語法結構,因此你不需要()去包裝他
還可以直接印出html的結構體,並且產生在頁面中

注意這段

echo "<h3>input:".$input."</h3>";

注意$input的左右各有一個 . ,這表示其實輸出的東西是三個字串的集合體

docker-compose

還記得我們昨天學過工程師都很懶
ㄜ,我是說我們昨天學過docker-compose吧
這時由於我們同時有nginx跟php的服務,因此可以使用docker-compose來運作他,減少我們的時間

nginx設定檔

先使用

docker container run --rm -it nginx:stable-alpine /bin/sh

進入到nginx的linux裡面,接著切換到下面的資料夾

/ # cd /etc/nginx/conf.d 
/etc/nginx/conf.d # ls
default.conf
/etc/nginx/conf.d # cat -n default.conf 

ls 這個linux指令可以讓你看到資料夾內的檔案

這裡我們可以看到設定檔只有default.conf一個

cat這個linux的指令可以將檔案的內容輸出到螢幕上
-n 則是顯示行號(可以選填)

由於#在nginx的設定檔內表示註解,因此實際上設定檔的內容如下

     1   server {
     2	    listen       80;
     3	    listen  [::]:80;
     4	    server_name  localhost;
     5	
     6	    #charset koi8-r;
     7	    #access_log  /var/log/nginx/host.access.log  main;
     8	
     9	    location / {
    10	        root   /usr/share/nginx/html;
    11	        index  index.html index.htm;
    12	    }
    13	
    14	    #error_page  404              /404.html;
    15	
    16	    # redirect server error pages to the static page /50x.html
    17	    #
    18	    error_page   500 502 503 504  /50x.html;
    19	    location = /50x.html {
    20	        root   /usr/share/nginx/html;
    21	    }
    22   }

有看到listen嗎?
這表示nginx監聽的port號(還記得我一開始說預設是80吧,就是這裡來的)

location表示檔案的位置
這裡的表示將/usr/share/nginx/html這個資料夾掛載到網址的/下面
而預設顯示的頁面則index指定,這裡指定為index.html
(多個檔案可以用空格隔開,當第一個檔案沒找到則會顯示第二個)

你可以使用

cat /usr/share/nginx/html/index.html

確認這個檔案是不是就是頁面上顯示的那個

後面則是error_page的指定頁面,這邊確認一下長什麼樣子就好

寫自己的nginx設定檔

由於我們想換掉原本預設顯示的頁面,因此我們需要自己寫設定檔
在你的工作資料夾下面開一個conf.d資料夾並建立一個docker.conf檔案如下

server {
    listen       80;
    server_name  localhost;

    location ~ \.php$ {
        fastcgi_pass   php:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /var/www/html/$fastcgi_script_name;
        include        fastcgi_params;
    }
}

這裡的

location ~ \.php$ {

表示我們要將.php檔案交給別人處理
移交出去的服務位置為php,port號為9000
初始頁面為index.php(其實可以不用設置,因為被location給攔截了)
而這段

fastcgi_param  SCRIPT_FILENAME  /var/www/html/$fastcgi_script_name;

則表示會根據在網址內輸入的名稱去找在/var/www/html下面相對應的檔案名稱
因此等一下我們需要把php檔給掛載在這個資料夾下面

docker-compose設定檔

好,我們終於要來做docker的設定檔了

在工作目錄建立一個docker-compose.yml檔如下

version: '3' 
services: 
  nginx:
    image: nginx:stable-alpine
    ports:
      - "8080:80" 
    volumes:
      - ./conf.d:/etc/nginx/conf.d 
    links:
      - myphp-fpm:php
  myphp-fpm:
    image: php:rc-fpm-alpine3.12 
    ports:
      - "9000:9000"
    volumes:
      - ./php:/var/www/html

nginx跟myphp-fpm就是我們兩個服務的名稱
image就是指定鏡像檔名稱,這裡可以替換成你們自己下載的鏡像檔名
ports跟之前玩的-p一樣,左側是本機port號,右側則是docker的port號
volumes則是container 相對應的 -v 指令
要注意的是links
我們將原本的myphp-fpm(也就是我們鏡像檔服務的名稱)對應到nginx的php名稱
因此在剛剛nginx設定檔的這一行

fastcgi_pass   php:9000;

其中php就是我們這裡指定的名字
而myphp-fpm的port號則是image原本就指定好的,我們不去做變動

注意兩個服務的volumes都是把我們工作目錄的資料夾掛載到兩個服務的資料夾
當前你的工作目錄應該長這樣(不是這樣的去改設定檔或是資料夾結構)

.
├── conf.d
│   └── docker.conf
├── php
│   └── index.php
└── docker-compose.yml

接著使用

docker-compose up

並且在網址列打上

http://localhost:8080/index.php

就可以看到你的服務拉

小結

php的語法算是相對簡單的,在js容易犯錯的字串也改由 . 替代
相對的設定伺服器跟docker反而複雜(看文字篇幅就知道了)

來複習一下php的語法吧

  • 基本結構
    • 使用html內嵌php的語法來寫網頁
  • 印出/讀取
    • 使用echo直接印出html結構並顯示在頁面上
    • 讀取時使用html的表單功能
  • 方法的結構
    • 動態型別,不必提前命名回傳值的型別
  • 邏輯控制
    • switch (跟C#類似)
    • if (跟C#類似)
  • 迴圈控制
    • for (跟C#類似)
    • while (跟C#類似)
  • 型別
    • 跟js一樣屬於動態+弱型別
  • 冪次計算 (跟C#類似)

好,那如果我只想架網站學到這就可以了嗎?

沒辦法,你之後還要去設定白名單讓別人可以透過ip連你
或是使用網址的方式(nginx可以幫你作反向代理,讓別人不知道你真正的ip在哪)
如果不想在自己的電腦上跑網站(不想整天電腦開著或是怕被攻擊)的話還需要去研究GCP等等網路空間讓你跑你的服務
所以你還要去學一下linux

蛤?好麻煩

當然,凡事沒有容易的

那為什麼說架設網站可以從php開始

因為php已經發展得相當好了,像是LAMP架構(linux Apache MySQL php)
甚至可以買一台NAS就直接跑起來,網路上也有很多專門給LAMP使用的空間
另外使用php的好處是小型專案不必像其他語言一樣使用MVC結構
可以迅速的開發好網站

MVC結構?

就是將網頁跟真正執行的程式碼分開的作法
維護上會比較方便,也可以讓前後端的工程師分開工作
像現在把html該呈現的東西與php的程式碼夾在一起其實並不是一個好的寫法
(不過不代表php不能這樣做,只是php可以選擇不這麼做來加速開發)

我們明天要學的網站架設第三大語言Ruby,最著名的框架Rails就嚴格遵守MVC架構


上一篇
docker 不是跨平台,我就是平台
下一篇
Ruby 那不是Rails的一個框架嗎?
系列文
你會十五種程式語言?不,我會十五種HelloWorld.為了避免這種狀況,因此寫了這篇:淺入淺出十五種程式語言30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
skycover
iT邦新手 4 級 ‧ 2020-09-15 22:14:28

如果有任何寫不清楚或是觀念沒有很明白的話請留言告知我
會盡快補上

如果有任何寫錯的地方也麻煩留言告知我
會盡快修正

感謝各位

我要留言

立即登入留言